package utils

/*
 * common configure reader.
 */

/*
 * config as:
   <main>
     c = 1
     b = 2
     <a>
      c = 1
     </a>
     #d = 1
   </main>
*/

import (
	"container/list"
	"fmt"
	"io/ioutil"
	"reflect"
	"strconv"
	"strings"
)

// node information.
type nodeInfo struct {
	name      string
	key2Value map[string]string
	children  map[string]*nodeInfo
}

// config struct.
type Config struct {
	root *nodeInfo
}

// parse from string.
func (c *Config) ParseFromString(content string) error {
	// act as stack.
	var st *list.List = list.New()

	lines := strings.Split(content, "\n")
	for i := 0; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" {
			continue
		}

		if line[0] == '#' || (len(line) >= 2 && line[0] == '/' && line[1] == '/') {
			// # and // is note.
			continue
		} else if line[0] == '<' {
			if line[len(line)-1] == '>' {
				if line[1] == '/' {
					// end a variable
					if st.Len() <= 0 {
						return fmt.Errorf("variable is not match error")
					}

					name := line[2 : len(line)-1]
					topNode := st.Back().Value.(*nodeInfo)
					if topNode.name != name {
						return fmt.Errorf("variable name is not the same")
					}

					if st.Len() == 1 {
						c.root = topNode
					}
					st.Remove(st.Back())
					if st.Len() == 0 {
						return nil
					}
				} else {
					// start a variable
					name := line[1 : len(line)-1]
					name = strings.TrimSpace(name)
					if name == "" {
						return fmt.Errorf("variable is empty")
					}

					if st.Len() > 0 {
						topNode := st.Back().Value.(*nodeInfo)
						key2Value := topNode.key2Value
						if _, ok := key2Value[name]; ok {
							return fmt.Errorf("find %s key repeated", name)
						}

						if _, ok := topNode.children[name]; ok {
							return fmt.Errorf("find %s node repeated", name)
						}

						// create a new node.
						newNode := &nodeInfo{
							name:      name,
							key2Value: map[string]string{},
							children:  map[string]*nodeInfo{},
						}
						topNode.children[name] = newNode
						st.PushBack(newNode)
					} else {
						// create a new node.
						newNode := &nodeInfo{
							name:      name,
							key2Value: map[string]string{},
							children:  map[string]*nodeInfo{},
						}
						st.PushBack(newNode)
					}
				}
			} else {
				return fmt.Errorf("find %s line's variable error", line)
			}
		} else {
			if st.Len() == 0 {
				return fmt.Errorf("error")
			}

			key2Value := strings.Split(line, "=")
			if len(key2Value) != 2 {
				return fmt.Errorf("variable is wrong,must a = b form")
			}

			key := strings.TrimSpace(key2Value[0])
			value := strings.TrimSpace(key2Value[1])
			if key == "" {
				return fmt.Errorf("key is empty")
			}

			topNode := st.Back().Value.(*nodeInfo)
			if _, ok := topNode.key2Value[key]; ok {
				return fmt.Errorf("find %s key repeated", key)
			}

			topNode.key2Value[key] = value
		}
	}
	return nil
}

// parse config.
func (c *Config) Parse(filePath string) error {
	if content, err := ioutil.ReadFile(filePath); err != nil {
		return err
	} else {
		return c.ParseFromString(string(content))
	}
}

// GetNode returns node path's value where the path is like "a/b/c"
func (c *Config) GetNode(path string) (string, bool) {
	allPath := strings.Split(path, "/")
	return c.find(allPath, c.root)
}

// find path's value as "config/a/b/c"
func (c *Config) find(path []string, node *nodeInfo) (string, bool) {
	if len(path) < 2 || node == nil {
		return "", false
	}

	if path[0] == node.name {
		if len(path) == 2 {
			if v, ok := node.key2Value[path[1]]; ok {
				return v, true
			} else {
				return "", false
			}
		} else {
			children, ok := node.children[path[1]]
			if ok {
				return c.find(path[1:], children)
			} else {
				return "", false
			}
		}
	} else {
		return "", false
	}
}

// get int32.
func (c *Config) GetNodeInt32(path string, defaultValue int32) int32 {
	ret, err := c.GetInt32(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}

// get int32.
func (c *Config) GetInt32(path string) (int32, error) {
	v, ret := c.GetNode(path)
	if ret {
		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return 0, err
		} else {
			return int32(val), nil
		}
	} else {
		return 0, fmt.Errorf("can not find %s", path)
	}
}

// get int.
func (c *Config) GetInt(path string) (int, error) {
	v, ret := c.GetNode(path)
	if ret {
		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return 0, err
		} else {
			return int(val), nil
		}
	} else {
		return 0, fmt.Errorf("can not find %s", path)
	}
}

// get uint32.
func (c *Config) GetNodeUint32(path string, defaultValue uint32) uint32 {
	ret, err := c.GetUint32(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}
func (c *Config) GetUint32(path string) (uint32, error) {
	v, ret := c.GetNode(path)
	if ret {
		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return 0, err
		} else {
			return uint32(val), nil
		}
	} else {
		return 0, fmt.Errorf("can not find %s", path)
	}
}

// get int64.
func (c *Config) GetNodeInt64(path string, defaultValue int64) int64 {
	ret, err := c.GetInt64(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}
func (c *Config) GetInt64(path string) (int64, error) {
	v, ret := c.GetNode(path)
	if ret {
		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return 0, err
		} else {
			return val, nil
		}
	} else {
		return 0, fmt.Errorf("can not find %s node", path)
	}
}

// get uint64.
func (c *Config) GetNodeUint64(path string, defaultValue uint64) uint64 {
	ret, err := c.GetUint64(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}
func (c *Config) GetUint64(path string) (uint64, error) {
	v, ret := c.GetNode(path)
	if ret {
		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return 0, err
		} else {
			return uint64(val), nil
		}
	} else {
		return 0, fmt.Errorf("can not find %s node", path)
	}
}

// get bool.
func (c *Config) GetNodeBool(path string, defaultValue bool) bool {
	ret, err := c.GetBool(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}
func (c *Config) GetBool(path string) (bool, error) {
	v, ret := c.GetNode(path)
	if ret {
		if v == "true" {
			return true, nil
		}
		if v == "false" {
			return false, nil
		}

		if val, err := strconv.ParseInt(v, 0, 64); err != nil {
			return false, err
		} else {
			return val > 0, nil
		}
	} else {
		return false, fmt.Errorf("can not find %s node", path)
	}
}

// get string.
func (c *Config) GetNodeString(path string, defaultValue string) string {
	ret, err := c.GetString(path)
	if err != nil {
		return defaultValue
	} else {
		return ret
	}
}
func (c *Config) GetString(path string) (string, error) {
	v, ok := c.GetNode(path)
	if ok {
		return v, nil
	} else {
		return "", fmt.Errorf("can not find %s node", path)
	}
}

// get vector for string.
func (c *Config) GetNodeVecString(path string, vec *[]string) error {
	ret, err := c.GetString(path)
	if err != nil {
		return err
	}

	vecRet := strings.Split(ret, ",")
	for i := 0; i < len(vecRet); i++ {
		if v := strings.TrimSpace(vecRet[i]); v != "" {
			*vec = append(*vec, v)
		}
	}
	return nil
}

// get vector for int64
func (c *Config) GetNodeVecInt(path string, vec *[]int64) error {
	ret, err := c.GetString(path)
	if err != nil {
		return err
	}

	vecRet := strings.Split(ret, ",")
	for i := 0; i < len(vecRet); i++ {
		if vVal, err := strconv.ParseInt(vecRet[i], 0, 64); err == nil {
			*vec = append(*vec, vVal)
		}
	}
	return nil
}

// get map string to string.
func (c *Config) GetNodeMapString2String(path string, k2v map[string]string) error {
	ret, err := c.GetString(path)
	if err != nil {
		return err
	}

	vecRet := strings.Split(ret, ",")
	for i := 0; i < len(vecRet); i++ {
		keys := strings.Split(vecRet[i], ":")
		if len(keys) >= 2 {
			k := strings.TrimSpace(keys[0])
			v := strings.TrimSpace(keys[1])
			k2v[k] = v
		}
	}
	return nil
}

// get map int64 to int64.
func (c *Config) GetNodeMapInt2Int(path string, k2v map[int64]int64) error {
	ret, err := c.GetString(path)
	if err != nil {
		return err
	}

	vecRet := strings.Split(ret, ",")
	for i := 0; i < len(vecRet); i++ {
		keys := strings.Split(vecRet[i], ":")
		if len(keys) >= 2 {
			k := strings.TrimSpace(keys[0])
			v := strings.TrimSpace(keys[1])
			if kVal, err1 := strconv.ParseInt(k, 0, 64); err1 == nil {
				if vVal, err2 := strconv.ParseInt(v, 0, 64); err2 == nil {
					k2v[kVal] = vVal
				}
			}
		}
	}
	return nil
}

// get map int64 to string.
func (c *Config) GetNodeMapInt2String(path string, k2v map[int64]string) error {
	ret, err := c.GetString(path)
	if err != nil {
		return err
	}

	vecRet := strings.Split(ret, ",")
	for i := 0; i < len(vecRet); i++ {
		keys := strings.Split(vecRet[i], ":")
		if len(keys) >= 2 {
			k := strings.TrimSpace(keys[0])
			v := strings.TrimSpace(keys[1])
			if kVal, err1 := strconv.ParseInt(k, 0, 64); err1 == nil {
				k2v[kVal] = v
			}
		}
	}
	return nil
}

// ParseNode parse a node's data to a struct, where nodeValue must be a point to a struct. such as:
/*
  <GuidRedis>
      addr = 192.168.115.40:6379
      index = 0
      password = u8B6hzV7j9ePg6M6
      repository = mlproj2017,mlart2017
  </GuidRedis>

  to golang struct:

  type GuidRedis struct {
        Addr       string `config:"addr"`
        Index      int    `config:"index"`
        Password   string `config:"password"`
        Repository string `config:"repository"`
  }
  where the struct has the same config tag.
*/
func (c *Config) ParseNode(nodePath string, nodeValue interface{}) error {
	paths := strings.Split(nodePath, "/")
	if len(paths) < 2 {
		return fmt.Errorf("%s format error", nodePath)
	}

	if node, err := c.findNode(paths, c.root); err != nil {
		return err
	} else {
		return c.parse(node, nodeValue)
	}
}

// setField 根据字段类型设置相应的值
func setSlice(field reflect.Value, value string) error {
	values := strings.Split(value, ",")
	sliceType := field.Type()
	slice := reflect.MakeSlice(sliceType, len(values), len(values))

	for i, v := range values {
		v = strings.TrimSpace(v)
		switch sliceType.Elem().Kind() {
		case reflect.String:
			slice.Index(i).SetString(v)

		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			intValue, err := strconv.ParseInt(v, 10, 64)
			if err != nil {
				return fmt.Errorf("invalid integer value in slice: %s", v)
			}
			slice.Index(i).SetInt(intValue)

		default:
			return fmt.Errorf("unsupported slice element type: %v", sliceType.Elem().Kind())
		}
	}

	field.Set(slice)
	return nil
}
func setField(field reflect.Value, value string) error {
	switch field.Kind() {
	case reflect.String:
		field.SetString(value)

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		i, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			return err
		}
		field.SetInt(i)

	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		i, err := strconv.ParseUint(value, 10, 64)
		if err != nil {
			return err
		}
		field.SetUint(i)

	case reflect.Float32, reflect.Float64:
		f, err := strconv.ParseFloat(value, 64)
		if err != nil {
			return err
		}
		field.SetFloat(f)

	case reflect.Bool:
		b, err := strconv.ParseBool(value)
		if err != nil {
			return err
		}
		field.SetBool(b)

	case reflect.Slice:
		if err := setSlice(field, value); err != nil {
			return err
		}

	default:
		return fmt.Errorf("unsupported type: %v", field.Type())
	}
	return nil
}
func (c *Config) parse(node *nodeInfo, value interface{}) error {
	v := reflect.ValueOf(value)
	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("config must be a pointer to a struct")
	}
	v = v.Elem()
	t := v.Type()

	fieldMap := make(map[string]reflect.Value)
	for i := 0; i < v.NumField(); i++ {
		field := t.Field(i)
		tagName := field.Tag.Get("config")
		if tagName == "" {
			tagName = strings.ToLower(field.Name)
		}
		fieldMap[tagName] = v.Field(i)
	}

	for key, value := range node.key2Value {
		field, ok := fieldMap[key]
		if !ok {
			continue
		}

		if field.IsValid() && field.CanSet() {
			if err := setField(field, value); err != nil {
				return fmt.Errorf("error setting field %s: %v", key, err)
			}
		}
	}
	return nil
}

// findNode find node and return nodeInfo or error
func (c *Config) findNode(path []string, node *nodeInfo) (*nodeInfo, error) {
	if len(path) < 2 || node == nil {
		return nil, fmt.Errorf("path format is not right:%+v", path)
	}

	if path[0] == node.name {
		if len(path) == 2 {
			if v, ok := node.children[path[1]]; ok {
				return v, nil
			} else {
				return nil, fmt.Errorf("can not find %s error", path[1])
			}
		} else {
			children, ok := node.children[path[1]]
			if ok {
				return c.findNode(path[1:], children)
			} else {
				return nil, fmt.Errorf("can not find %s node", path[1])
			}
		}
	} else {
		return nil, fmt.Errorf("can not find %s node", path[0])
	}
}
